/*
 * Copyright (C) 2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package top.xbaistack.game;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections.CollectionUtils;
import top.xbaistack.common.AxisDirection;
import top.xbaistack.common.AxisSupplier;
import top.xbaistack.exception.BaseException;
import top.xbaistack.utils.Assert;
import top.xbaistack.utils.Utils;

import javax.websocket.Session;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 游戏房间
 *
 * @author xbaistack
 * @since 2025/03/25
 */
@Getter
@Setter
public class Room {

    /**
     * 房间ID，UUID
     */
    private String id;

    /**
     * 棋盘行数
     */
    private int rows;

    /**
     * 棋盘列数
     */
    private int cols;

    /**
     * 棋盘是否冻结，根据是否结束，以及用户是否可以冻结来决定，防止在游戏过程中用户刷新网页造成可以继续落子的情况。
     */
    private boolean frozen;

    /**
     * 游戏是否结束
     */
    private boolean over;

    /**
     * 当前游戏状态，普通时候是“游戏中”，完结之后值是“我方胜”之类的提示。
     */
    private String result = "游戏中...";

    /**
     * 核心棋盘数据（二维数组）
     */
    private ChessPiece[][] chessboard;

    /**
     * 当前房间的所有玩家（五子棋的话就两个，你和对方）
     */
    @JsonIgnore
    private List<Player> players;

    public static Room create(int rows, int cols) {
        Room room = new Room();
        room.setId(Utils.generatedId());
        room.setRows(rows);
        room.setCols(cols);
        ChessPiece[][] chessboard = new ChessPiece[ rows ][ cols ];
        for (int x = 0; x < rows; x++) {
            ChessPiece[] row = new ChessPiece[ cols ];
            for (int y = 0; y < cols; y++) {
                row[ y ] = new ChessPiece();
            }
            chessboard[ x ] = row;
        }
        room.setChessboard(chessboard);
        return room;
    }

    /**
     * 玩家进入房间
     *
     * @param player 玩家
     */
    public void addPlayer(Player player) {
        if (CollectionUtils.isEmpty(players)) {
            players = new ArrayList<>(2);
        }
        players.add(player);
    }

    public Player getPlayer(String id) {
        if (CollectionUtils.isNotEmpty(players)) {
            for (Player player : players) {
                if (Objects.equals(player.getId(), id)) {
                    return player;
                }
            }
        }
        return null;
    }

    /**
     * 获取除了我之外的其他玩家（也就是对方）
     *
     * @param me 我的用户ID
     * @return 其他玩家列表
     */
    public List<Player> getOtherPlayers(String me) {
        return players.stream().filter(player -> !Objects.equals(player.getId(), me)).collect(Collectors.toList());
    }

    /**
     * 用于打印测试棋盘数组的数据
     *
     * @return
     */
    public String pretty() {
        StringBuilder builder = new StringBuilder();
        for (int x = 0; x < rows; x++) {
            builder.append("[");
            for (int y = 0; y < cols; y++) {
                builder.append(x + "," + y + "(" + chessboard[ x ][ y ].getRole() + ")");
            }
            builder.append("]\n");
        }
        return builder.toString();
    }

    /**
     * 玩家上线后加入房间
     *
     * @param playerId 玩家ID
     * @param session  玩家的 WebSocket 连接，保存下来方便后面推送消息
     */
    public void join(String playerId, Session session) {
        Assert.isFalse(CollectionUtils.isEmpty(players), "房间信息异常！");
        for (Player player : players) {
            if (Objects.equals(player.getId(), playerId)) {
                player.setSession(session);
                break;
            }
        }
    }

    /**
     * 玩家离线，当所有玩家中离线数量为0时，自动解散房间。
     *
     * @param playerId 玩家ID
     * @return
     */
    public boolean offline(String playerId) {
        Assert.isFalse(CollectionUtils.isEmpty(players), "房间信息异常！");
        for (Player player : players) {
            if (Objects.equals(player.getId(), playerId)) {
                player.offline();
            }
        }
        long online = players.stream().filter(Player::isOnline).count();
        if (online == 0) {
            return true;
        }
        return false;
    }

    /**
     * 循环除了特定玩家之外的所有玩家
     *
     * @param player   玩家ID
     * @param consumer 消费者
     */
    public void exclude(String player, Consumer<Player> consumer) {
        try {
            Thread.sleep(50);
            for (Player p : players) {
                if (!Objects.equals(p.getId(), player)) {
                    consumer.accept(p);
                }
            }
        } catch (InterruptedException e) {
            throw new BaseException("服务异常!");
        }
    }

    /**
     * 获取所有在线的玩家列表，方便向在线玩家推送公开消息。
     *
     * @return
     */
    @JsonIgnore
    public List<Player> getOnlinePlayers() {
        return players.stream().filter(Player::isOnline).collect(Collectors.toList());
    }

    /**
     * 玩家落子
     *
     * @param role 玩家所在的角色（黑子、白子）
     * @param x    棋子X坐标
     * @param y    棋子Y坐标
     */
    public void moveTo(Role role, int x, int y) {
        Assert.isTrue(x >= 0 && x < rows, "无法<横向>移动到此位置！");
        Assert.isTrue(y >= 0 && x < cols, "无法<纵向>移动到此位置！");
        ChessPiece piece = chessboard[ x ][ y ];
        piece.setRole(role);
        System.out.println(pretty());
    }

    /**
     * 没落一次子都需要判断当前游戏是否结束
     *
     * @param role 玩家所在的角色（黑子、白子）
     * @param x    棋子X坐标
     * @param y    棋子Y坐标
     * @return 是否游戏结束
     */
    public boolean isFinished(Role role, int x, int y) {
        // 依次判断四个大方向上是否有满足凑齐五个棋子的条件，
        // 但凡是某一个方向满足五个棋子就立即返回 true，
        // 四个方向依次是：水平（─）、垂直（|）、左上到右下（╲）、左下到右上（╱）；
        Predicate<ChessPiece> tester = piece -> piece.getRole() == role;
        for (AxisDirection direction : directions) {
            int left = countPieceNumber(direction.getLeft(), x, y, tester);
            int right = countPieceNumber(direction.getRight(), x, y, tester);
            if ((left > 0 || right > 0) && left + right + 1 >= 5) { // 5 = left-count + 1 + right-count
                return true;
            }
        }
        return false;
    }

    /**
     * 统计单一方向上的棋子数量（左侧|右侧|上|下|左上|左下|右上|右下）
     *
     * @param supplier 用于提供下一个坐标
     * @param x        棋子X坐标
     * @param y        棋子Y坐标
     * @param tester   用于校验目标坐标是否是符号条件的棋子
     * @return 同色系棋子的数量
     */
    private int countPieceNumber(AxisSupplier supplier, int x, int y, Predicate<ChessPiece> tester) {
        int count = 0;
        int[] axis = supplier.accept(x, y);
        while (axis[ 0 ] >= 0 && axis[ 0 ] < rows && axis[ 1 ] >= 0 && axis[ 1 ] < cols) {
            ChessPiece item = chessboard[ axis[ 0 ] ][ axis[ 1 ] ];
            if (tester.test(item)) {
                count++;
                axis = supplier.accept(axis[ 0 ], axis[ 1 ]);
            } else break;
        }
        return count;
    }

    private static final List<AxisDirection> directions = new ArrayList<>(4);

    static {
        // 水平
        // [ 0, 0, 0, 0, 0 ]
        // [ 0, 0, 0, 0, 0 ]
        // [ #, #, #, #, # ]
        // [ 0, 0, 0, 0, 0 ]
        // [ 0, 0, 0, 0, 0 ]
        AxisDirection direction = new AxisDirection();
        direction.setLeft((x, y) -> new int[] { x, y - 1 });
        direction.setRight((x, y) -> new int[] { x, y + 1 });
        directions.add(direction);
        // 垂直
        // [ 0, 0, #, 0, 0 ]
        // [ 0, 0, #, 0, 0 ]
        // [ 0, 0, #, 0, 0 ]
        // [ 0, 0, #, 0, 0 ]
        // [ 0, 0, #, 0, 0 ]
        direction = new AxisDirection();
        direction.setLeft((x, y) -> new int[] { x - 1, y });
        direction.setRight((x, y) -> new int[] { x + 1, y });
        directions.add(direction);
        // 左上到右下
        // [ #, 0, 0, 0, 0 ]
        // [ 0, #, 0, 0, 0 ]
        // [ 0, 0, #, 0, 0 ]
        // [ 0, 0, 0, #, 0 ]
        // [ 0, 0, 0, 0, # ]
        direction = new AxisDirection();
        direction.setLeft((x, y) -> new int[] { x - 1, y - 1 });
        direction.setRight((x, y) -> new int[] { x + 1, y + 1 });
        directions.add(direction);
        // 左下到右上
        // [ 0, 0, 0, 0, # ]
        // [ 0, 0, 0, #, 0 ]
        // [ 0, 0, #, 0, 0 ]
        // [ 0, #, 0, 0, 0 ]
        // [ #, 0, 0, 0, 0 ]
        direction = new AxisDirection();
        direction.setLeft((x, y) -> new int[] { x + 1, y - 1 });
        direction.setRight((x, y) -> new int[] { x - 1, y + 1 });
        directions.add(direction);
    }

}
